/*-------------------------------------------------------------------------
 *
 * mcxtfuncs.c
 *	  显示后端内存上下文的函数。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/mcxtfuncs.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "funcapi.h"
#include "miscadmin.h"
#include "mb/pg_wchar.h"
#include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/builtins.h"

/* ----------
 * 显示MemoryContext标识符的最大字节数。
 * ----------
 */
#define MEMORY_CONTEXT_IDENT_DISPLAY_SIZE	1024

/*
 * PutMemoryContextsStatsTupleStore
 *		用于pg_get_backend_memory_contexts的一个递归级别。
 */
static void fc_PutMemoryContextsStatsTupleStore(Tuplestorestate *fc_tupstore,
								 TupleDesc fc_tupdesc, MemoryContext fc_context,
								 const char *fc_parent, int fc_level)
{
#define PG_GET_BACKEND_MEMORY_CONTEXTS_COLS	9

	Datum		fc_values[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS];
	bool		fc_nulls[PG_GET_BACKEND_MEMORY_CONTEXTS_COLS];
	MemoryContextCounters fc_stat;
	MemoryContext fc_child;
	const char *fc_name;
	const char *fc_ident;

	AssertArg(MemoryContextIsValid(fc_context));

	fc_name = fc_context->name;
	fc_ident = fc_context->ident;

	/*
	 * 为了与日志输出保持一致，我们将dynahash上下文标记为
	 * 仅使用哈希表名称，与MemoryContextStatsPrint()相同。
	 */
	if (fc_ident && strcmp(fc_name, "dynahash") == 0)
	{
		fc_name = fc_ident;
		fc_ident = NULL;
	}

	/* 检查上下文本身 */
	memset(&fc_stat, 0, sizeof(fc_stat));
	(*fc_context->methods->stats) (fc_context, NULL, (void *) &fc_level, &fc_stat, true);

	memset(fc_values, 0, sizeof(fc_values));
	memset(fc_nulls, 0, sizeof(fc_nulls));

	if (fc_name)
		fc_values[0] = CStringGetTextDatum(fc_name);
	else
		fc_nulls[0] = true;

	if (fc_ident)
	{
		int			fc_idlen = strlen(fc_ident);
		char		fc_clipped_ident[MEMORY_CONTEXT_IDENT_DISPLAY_SIZE];

		/*
		 * 一些标识符，比如SQL查询字符串，可能会非常长，
		 * 截断超大标识符。
		 */
		if (fc_idlen >= MEMORY_CONTEXT_IDENT_DISPLAY_SIZE)
			fc_idlen = pg_mbcliplen(fc_ident, fc_idlen, MEMORY_CONTEXT_IDENT_DISPLAY_SIZE - 1);

		memcpy(fc_clipped_ident, fc_ident, fc_idlen);
		fc_clipped_ident[fc_idlen] = '\0';
		fc_values[1] = CStringGetTextDatum(fc_clipped_ident);
	}
	else
		fc_nulls[1] = true;

	if (fc_parent)
		fc_values[2] = CStringGetTextDatum(fc_parent);
	else
		fc_nulls[2] = true;

	fc_values[3] = Int32GetDatum(fc_level);
	fc_values[4] = Int64GetDatum(fc_stat.totalspace);
	fc_values[5] = Int64GetDatum(fc_stat.nblocks);
	fc_values[6] = Int64GetDatum(fc_stat.freespace);
	fc_values[7] = Int64GetDatum(fc_stat.freechunks);
	fc_values[8] = Int64GetDatum(fc_stat.totalspace - fc_stat.freespace);
	tuplestore_putvalues(fc_tupstore, fc_tupdesc, fc_values, fc_nulls);

	for (fc_child = fc_context->firstchild; fc_child != NULL; fc_child = fc_child->nextchild)
	{
		fc_PutMemoryContextsStatsTupleStore(fc_tupstore, fc_tupdesc,
										 fc_child, fc_name, fc_level + 1);
	}
}

/*
 * pg_get_backend_memory_contexts
 *		显示后端内存上下文的SQL SRF。
 */
Datum pg_get_backend_memory_contexts(PG_FUNCTION_ARGS)
{
	ReturnSetInfo *fc_rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;

	InitMaterializedSRF(fcinfo, 0);
	fc_PutMemoryContextsStatsTupleStore(fc_rsinfo->setResult, fc_rsinfo->setDesc,
									 TopMemoryContext, NULL, 0);

	return (Datum) 0;
}

/*
 * pg_log_backend_memory_contexts
 *		信号后端或辅助进程记录其内存上下文。
 *
 * 默认情况下，仅超级用户被允许发出信号以记录内存
 * 上下文，因为允许任何用户在无上限的速率下发出此请求
 * 将会产生大量日志消息，可能导致服务拒绝。可以通过GRANT
 * 允许其他角色。
 *
 * 在收到此信号时，后端或辅助进程在信号处理程序中设置标志，
 * 这会导致下一个CHECK_FOR_INTERRUPTS()
 * 或特定进程的中断处理程序记录内存上下文。
 */
Datum pg_log_backend_memory_contexts(PG_FUNCTION_ARGS)
{
	int			fc_pid = PG_GETARG_INT32(0);
	PGPROC	   *fc_proc;
	BackendId	fc_backendId = InvalidBackendId;

	fc_proc = BackendPidGetProc(fc_pid);

	/*
	 * 查看给定pid的进程是否是后端或辅助进程。
	 *
	 * 如果给定进程是后端，稍后在SendProcSignal()中使用其后端id
	 * 来加速操作。否则，不这么做，因为辅助进程（除了启动进程）
	 * 没有有效的后端id。
	 */
	if (fc_proc != NULL)
		fc_backendId = fc_proc->backendId;
	else
		fc_proc = AuxiliaryPidGetProc(fc_pid);

	/*
	 * 如果pid无效，BackendPidGetProc()和AuxiliaryPidGetProc()将返回NULL；
	 * 但当我们到达kill()时，一个我们这里得到有效proc的进程可能已自行终止。
	 * 没有办法对任意进程获取锁以防止这种情况。但是，由于
	 * 该机制通常用于调试一个正在运行并消耗大量内存的后端或辅助
	 * 进程，因此它可能会先自行结束并且其内存上下文未被记录并不是问题。
	 */
	if (fc_proc == NULL)
	{
		/*
		 * 这只是一个警告，因此在循环遍历结果集时，如果某个后端在运行过程中自行终止，则不会中止。
		 */
		ereport(WARNING,
				(errmsg("PID %d is not a PostgreSQL server process", fc_pid)));
		PG_RETURN_BOOL(false);
	}

	if (SendProcSignal(fc_pid, PROCSIG_LOG_MEMORY_CONTEXT, fc_backendId) < 0)
	{
		/* 再次，仅为允许循环而发出警告 */
		ereport(WARNING,
				(errmsg("could not send signal to process %d: %m", fc_pid)));
		PG_RETURN_BOOL(false);
	}

	PG_RETURN_BOOL(true);
}
